실행컨텍스트와 Scope Chain 그리고 Closure

Scope Chain

실행컨텍스트 생성단계를 먼저 살펴보자,
JS Engine은 function 키워드를 만나자마자, 실행컨텍스트에 넣을 Function Object를 만든다.
이때 Function Object의 내부 프로퍼티 [[Scope]]에 함수가 선언된 Scope를 저장한다.

{
  "렉시컬환경컴포넌트_LEC": {
    "환경레코드": {
      "선언적환경레코드": {},
      "오브젝트환경레코드": {}
    },
    "외부 렉시컬 환경 참조": {}
  }
}

[[Scope]]는 실행컨텍스트의 외부 렉시컬 환경 참조에 바인딩된다.
이렇게 각 함수들은 자신의 부모부터 Root까지 연결되어 있는데, 이것을 Scope Chain 이라고 부른다.

식별자 찾기

실행 컨텍스트는 변수를 찾을 때, 현재의 컨텍스트에 해당 변수나 함수가 없다면
**[[Scope]]**로 이동해 식별자를 찾아나선다.
이렇게 끝까지 찾다가 Root에도 없으면 찾을 수 없다는 에러가 발생하게 된다.


Closure

함수 내부에 있는 변수는, 함수가 호출될 때마다 초기화되는 것이 기본이다. 즉, 일반적인 함수라면 한 번 실행되고 나면 내부 지역 변수들은 모두 사라진다.

하지만 우리는 아래와 같은 패턴이 동작하는 것을 보게 된다.

function counter() {
  let count = 0;
  return function () {
    return ++count;
  };
}

adder = counter();
adder(); // 1
adder(); // 2

count는 이미 counter() 호출이 끝난 시점에서 사라져야 하지만, 여전히 내부 함수는 count에 접근할 수 있다.

이 현상이 바로 클로저(Closure) 다.

JS 엔진이 function 키워드를 만나면 미리 Function Object를 만들고,
그 내부 프로퍼티 [[Scope]]함수가 선언된 시점의 외부 렉시컬 환경 참조를 저장한다고 했었다.
그리고 실행될 때 그 기억된 외부 스코프를 따라 Scope Chain으로 식별자를 찾는다.
함수가 종료되어도 “렉시컬 환경 객체”는 참조 중이므로 GC 대상이 되지 않고 남는다.

IIFE (Immediately Invoked Function Expression)

클로저 사용에서 자주 등장하는 패턴이 바로 즉시 실행 함수(IIFE) 다.

const counter = (function () {
  let count = 0;
  return function () {
    return ++count;
  };
})();

counter(); // 1
counter(); // 2

IIFE는 함수를 정의하자마자 즉시 호출한다.

() 괄호 연산자는 내부를 즉시 반환한다. 즉, 여기서 함수 선언문은 표현식으로 바뀐다. 그래서 (function ()) 처럼 괄호가 아니라 +function(){}() 형태로 연산자를 붙여서 표현식으로 만들 수도 있다.
그리고 나서 뒤에 붙은 ()로 함수를 호출한다.

한 번 실행된 후 내부 변수(count)는 외부에서 직접 접근할 수 없고, 오직 반환된 내부 함수만 접근할 수 있다. 클로저로 private 변수를 만드는 고전적인 패턴으로 모듈시스템이 없던 시절에, IIFE를 이용하여 스코프를 분리했다.